/**
 * 
 */
package ro.dta.idbi.ui;

import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.beans.PropertyVetoException;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.*;
import javax.swing.border.BevelBorder;
import javax.swing.border.Border;

import ro.dta.idbi.interfaces.IMemento;
import ro.dta.idbi.interfaces.ITransformationResult;
import ro.dta.idbi.interfaces.IntegrationOperation;
import ro.dta.idbi.interfaces.ui.IMainFrame;
import ro.dta.idbi.interfaces.ui.IProjectPane;
import ro.dta.idbi.ui.sql.SchemaChangedEvent;
import ro.dta.idbi.ui.sql.SchemaChangedListener;
import ro.dta.idbi.ui.sql.SchemaUndoEvent;
import ro.dta.idbi.ui.sql.SchemaUndoListener;
import ro.dta.idbi.ui.sql.SqlSchemaPanel;
import uk.ac.ic.doc.automed.NotFoundException;
import uk.ac.ic.doc.automed.reps.Schema;

/**
 * Integration panel used to display several schemas and guide the user through
 * the integration process (conforming, merging, improvement)
 * 
 * @author Tudor Dobrila
 * 
 */
public class IntegrationPanel extends JPanel {

	private static final long serialVersionUID = -6075840862964871953L;

	private static final Logger LOGGER = Logger.getLogger("ro.dta.idbi.ui.IntegrationPanel");

	/**
	 * Desktop pane holding the internal frames
	 */
	private transient JDesktopPane desktop;

	/**
	 * Internal frames holding the SQL schema diagrams
	 */
	private final transient JInternalFrame[] frames;

	/**
	 * Show / hide internal frames buttonns
	 */
	private transient JFrameButton[] buttons;

	/**
	 * Strategy panel
	 */
	private transient JPanel strategyPanel;

	/**
	 * Hide strategy button
	 */
	private transient JButton hideStrBtn;

	/**
	 * Project pane holding this integration panel
	 */
	private transient final IProjectPane projectPane;

	/**
	 * Strategy diagram label
	 */
	private transient JLabel strategyLabel;

	/**
	 * Zoom in / out of the strategy diagram button
	 */
	private transient JButton zoomBtn;
	
	/**
	 * Button used to proceed to the next integration step
	 */
	private transient JButton proceedBtn;

	/**
	 * Integration panel, showing side by side schemas that are to be integrated
	 * 
	 * @param parentFrame
	 *            Parent frame of the application
	 * @param stepSchemas
	 *            Original schemas integrated in current step
	 * @param crtSchemas
	 *            Currently integrated schemas
	 * @param operation
	 *            Current stage in the integration process, can either be
	 *            CONFORMING, MERGING or IMPROVING
	 * @throws Exception
	 */
	public IntegrationPanel(final IMainFrame parentFrame, final String[] stepSchemas,
			final String[] crtSchemas, final IntegrationOperation operation) throws NotFoundException {
		this(parentFrame, null, stepSchemas, crtSchemas, operation);
	}

	/**
	 * Integration panel, showing side by side schemas that are to be integrated
	 * 
	 * @param parentFrame
	 *            Parent frame of the application
	 * @param projectPane
	 *            Project pane containing this integration panel
	 * @param stepSchemas
	 *            Original schemas integrated in current step
	 * @param crtSchemas
	 *            Currently integrated schemas
	 * @param operation
	 *            Current stage in the integration process, can either be
	 *            CONFORMING, MERGING or IMPROVING
	 * @throws NotFoundException
	 */
	public IntegrationPanel(final IMainFrame parentFrame, final IProjectPane projectPane,
			final String[] stepSchemas, final String[] crtSchemas, final IntegrationOperation operation)
			throws NotFoundException {
		super();
		if (operation == IntegrationOperation.CONFORMING && stepSchemas.length != crtSchemas.length) {
			throw new IllegalArgumentException("stepSchemas and crtSchemas must be of the same size.");
		}

		this.projectPane = projectPane;
		setLayout(new BorderLayout());

		// Desktop that will hold the internal frames
		desktop = new JDesktopPane();
		final int count = crtSchemas.length;
		final IntegrationOperation crtOp = projectPane.getProject().getCurrentOperation();

		buttons = new JFrameButton[count];
		for (int i = 0; i < count; i++) {
			buttons[i] = new JFrameButton();
		}

		// Create internal frames
		final Point point = new Point((count - 1) * 15, (count - 1) * 15);
		frames = new JInternalFrame[count];

		for (int i = count - 1; i >= 0; i--) {
			final JFrameButton crtButton = buttons[i];
			final String origTitle = stepSchemas[i];
			final String title = origTitle + " (" + crtSchemas[i] + ")";
			final JInternalFrame crtFrame = new JInternalFrame(title, true, false, true, true) {

				private static final long serialVersionUID = -3841537887505660815L;

				@Override
				public void setIcon(final boolean newIcon) throws PropertyVetoException {
					setVisible(!newIcon);
					isIcon = newIcon;
					crtButton.changeColor();
				}

				@Override
				public void setVisible(final boolean paramBoolean) {
					crtButton.changeColor();
					super.setVisible(paramBoolean);
				}
			};
			frames[i] = crtFrame;
			frames[i].setSize(300, 300);
			frames[i].setLocation(point);
			final Schema schema = Schema.getSchema(crtSchemas[i]);
			final SqlSchemaPanel diagram;
			try {
				diagram = new SqlSchemaPanel(parentFrame, schema, projectPane.getProject());
			} catch (Exception e) {
				JOptionPane.showMessageDialog(this, "Could not load schema " + crtSchemas[i]
						+ ". Integration cannot continue!");
				return;
			}

			// Listen for the schema changed event, usually fired after some
			// transformation has been applied over the schema
			diagram.getDiagram().addSchemaChangedEventListener(new SchemaChangedListener() {

				@Override
				public void schemaChanged(final SchemaChangedEvent event) {
					final ITransformationResult res = event.getResult();
					if (res != null) {
						diagram.setDiagram(res.getTransSchema(), crtOp);
						crtFrame.setTitle(origTitle + " (" + res.getTransSchema().getName() + ")");
						projectPane.getProject().replaceCrtStepItem(res.getOriginalSchema().getName(),
								res.getTransSchema().getName(), res.getTransCmd().get(0));
						parentFrame.getHistoryPanel().addCommand(res);
					}
				}
			});
			
			diagram.addSchemaUndoEventListener(new SchemaUndoListener() {
				
				@Override
				public void schemaUndo(SchemaUndoEvent event) {
					IMemento oldSchema = event.getRestoredSchema();
					crtFrame.setTitle(origTitle + " (" + oldSchema.getOriginalSchema() + ")");
					crtButton.refresh();
				}
			});
			
			frames[i].setVisible(true);

			desktop.add(frames[i]);
			frames[i].getContentPane().setLayout(new BorderLayout());
			frames[i].getContentPane().add(diagram, BorderLayout.CENTER);
			point.translate(-15, -15);
		}

		strategyPanel = new JPanel(new BorderLayout());
		strategyPanel.setPreferredSize(new Dimension(170, 170));
		strategyPanel.setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED));
		final ImageIcon strategyImg = new ImageIcon(projectPane.getProject().getStrategyImage()
				.getScaledInstance(150, -1, BufferedImage.SCALE_SMOOTH));
		strategyLabel = new JLabel(strategyImg);
		strategyPanel.add(strategyLabel, BorderLayout.CENTER);

		zoomBtn = new JButton("Zoom In", new ImageIcon("./resources/zoom_in.png"));
		zoomBtn.addMouseListener(new ZoomMouseAdapter());
		strategyPanel.add(zoomBtn, BorderLayout.NORTH);
		strategyPanel.setLocation(0, 0);
		strategyPanel.setSize(strategyPanel.getPreferredSize());
		desktop.add(strategyPanel, Integer.valueOf(10));

		add(desktop, BorderLayout.CENTER);

		final JToolBar schemasBar = new JToolBar();
		schemasBar.setOrientation(JToolBar.HORIZONTAL);
		schemasBar.setFloatable(false);
		final Border outer = BorderFactory.createLineBorder(Color.GRAY);
		final Border inner = BorderFactory.createEmptyBorder(5, 5, 5, 5);
		schemasBar.setBorder(BorderFactory.createCompoundBorder(outer, inner));

		final JButton autoArrangeBtn = new JButton("Auto Arrange", new ImageIcon("./resources/arrange.png"));
		autoArrangeBtn.setToolTipText("Auto-arrange the open schemas");
		autoArrangeBtn.addMouseListener(new ArrangeMouseAdapter());
		schemasBar.add(autoArrangeBtn);

		final JButton hideAllBtn = new JButton("Hide All");
		hideAllBtn.setToolTipText("Hide all open schemas");
		hideAllBtn.addMouseListener(new HideAllMouseAdapter());
		schemasBar.add(hideAllBtn);

		hideStrBtn = new JButton("Hide strategy");
		hideStrBtn.setToolTipText("Show / hide strategy");
		hideStrBtn.addMouseListener(new HideStrategyMouseAdapter());
		schemasBar.add(hideStrBtn);
		schemasBar.add(new JSeparator(JSeparator.VERTICAL));

		for (int i = 0; i < count; i++) {
			buttons[i].setFrame(frames[i]);
			schemasBar.add(buttons[i]);
		}

		if (projectPane != null) {
			final JSeparator separ = new JSeparator(JSeparator.VERTICAL);
			separ.setPreferredSize(new Dimension(2, 20));
			separ.setMaximumSize(separ.getPreferredSize());
			schemasBar.add(separ);

			String nextPhase = "";
			switch (projectPane.getProject().getCurrentOperation()) {
			case CONFORMING:
				nextPhase = "merging";
				break;
			case MERGING:
				nextPhase = "improvement";
				break;
			case IMPROVEMENT:
				nextPhase = "next integration step";
				break;
			default:
			}

			boolean lastStep = projectPane.getProject().isLastStep();
			proceedBtn = new JButton(lastStep ? "Finalise integration" : "Proceed to " + nextPhase);
			proceedBtn.setForeground(new Color(0, 89, 156));
			proceedBtn.setIcon(new ImageIcon("./resources/proceed.png"));
			// proceedBtn.setPreferredSize(new Dimension(110,
			// proceedBtn.getHeight()));
			proceedBtn.setToolTipText("Proceed to the next integration phase");
			proceedBtn.addActionListener(new ActionListener() {

				@Override
				public void actionPerformed(final ActionEvent event) {
					if (JOptionPane.showConfirmDialog(IntegrationPanel.this,
							"Are you sure you want to proceed?", "Confirm", JOptionPane.YES_NO_CANCEL_OPTION) == JOptionPane.YES_OPTION) {
						projectPane.next();
					}
				}
			});
			schemasBar.add(proceedBtn);
		}

		add(schemasBar, BorderLayout.NORTH);
	}

	@Override
	public void paint(final Graphics graphics) {
		super.paint(graphics);

		if (strategyPanel != null) {
			final Dimension dim1 = desktop.getSize();
			final Dimension dim2 = strategyPanel.getSize();
			final int newX = (int) (dim1.getWidth() - dim2.getWidth());
			final int newY = (int) (dim1.getHeight() - dim2.getHeight());
			strategyPanel.setLocation(newX, newY);
		}
	}
	
	/**
	 * Disable the proceed button
	 */
	public void disableProceedButton() {
		proceedBtn.setEnabled(false);
	}

	/**
	 * Arrange internal frames mouse adapter
	 * 
	 * @author Tudor Dobrila
	 * 
	 */
	class ArrangeMouseAdapter extends MouseAdapter {
		@Override
		public void mouseReleased(final MouseEvent event) {
			final int noChild = desktop.getComponentCount();
			int noFrames = 0;

			for (int i = 0; i < noChild; i++) {
				if (desktop.getComponent(i) instanceof JInternalFrame) {
					final JInternalFrame crtFrame = (JInternalFrame) desktop.getComponent(i);

					if (!crtFrame.isIcon()) {
						noFrames++;
					}
				}
			}

			if (noFrames != 0) {
				final int cols = (int) Math.sqrt(noFrames);
				final int rows = (int) Math.ceil(noFrames / (double) cols);
				final int width = desktop.getWidth() / rows;
				final int height = desktop.getHeight() / cols;
				int count = 0;

				for (int i = 0; i < noChild; i++) {
					if (desktop.getComponent(i) instanceof JInternalFrame) {
						final JInternalFrame crtFrame = (JInternalFrame) desktop.getComponent(i);

						if (!crtFrame.isIcon()) {
							final int crtRow = count / cols;
							final int crtCol = count % cols;

							crtFrame.setLocation(crtRow * width, crtCol * height);
							crtFrame.setSize(width, height);

							count++;
						}
					}
				}
			}
		}
	}

	/**
	 * Hide all frames mouse adapter
	 * 
	 * @author Tudor Dobrila
	 * 
	 */
	class HideAllMouseAdapter extends MouseAdapter {
		@Override
		public void mouseReleased(final MouseEvent paramMouseEvent) {
			for (int i = 0; i < frames.length; i++) {
				try {
					frames[i].setIcon(true);
				} catch (PropertyVetoException e) {
					LOGGER.log(Level.WARNING, "Could not change JInternalFrame icon", e);
				}
			}
		}
	}

	/**
	 * Hide / show strategy mouse adapter
	 * 
	 * @author Tudor Dobrila
	 * 
	 */
	class HideStrategyMouseAdapter extends MouseAdapter {
		@Override
		public void mouseReleased(final MouseEvent event) {
			strategyPanel.setVisible(!strategyPanel.isVisible());
			if (strategyPanel.isVisible()) {
				hideStrBtn.setText("Hide strategy");
			} else {
				hideStrBtn.setText("Show strategy");
			}
		}
	}

	/**
	 * Zoom in / out strategy mouse adapter
	 * 
	 * @author Tudor Dobrila
	 * 
	 */
	class ZoomMouseAdapter extends MouseAdapter {
		@Override
		public void mouseReleased(final MouseEvent event) {
			if (zoomBtn.getText().equals("Zoom In")) {
				final BufferedImage strategyImg = projectPane.getProject().getStrategyImage();
				strategyPanel.setSize(strategyImg.getWidth() + 5, strategyImg.getHeight() + 35);
				strategyLabel.setIcon(new ImageIcon(strategyImg));
				zoomBtn.setText("Zoom Out");
			} else {
				strategyPanel.setSize(new Dimension(170, 170));
				final ImageIcon strategyImg = new ImageIcon(projectPane.getProject().getStrategyImage()
						.getScaledInstance(150, -1, BufferedImage.SCALE_SMOOTH));
				strategyLabel.setIcon(strategyImg);
				zoomBtn.setText("Zoom In");
			}

			repaint();
		}
	}

	/**
	 * Button element associated with a JInternalFrame
	 * 
	 * Used in the left toolbar to show / hide various components
	 * 
	 * @author Tudor Dobrila
	 * 
	 */
	static class JFrameButton extends JButton implements ActionListener {

		private static final long serialVersionUID = 7245756956569445632L;
		private static final ImageIcon SCHEMA_ICON = new ImageIcon("./resources/schema.png");
		private JInternalFrame frame;

		/**
		 * JFrameButton class constructor
		 */
		public JFrameButton() {
			super();
			setIcon(SCHEMA_ICON);
			addActionListener(this);
		}

		/**
		 * JFrameButton class constructor
		 * 
		 * @param frame
		 *            Frame associated with the button
		 */
		public JFrameButton(final JInternalFrame frame) {
			this();
			this.frame = frame;
			refresh();
		}

		@Override
		public void actionPerformed(final ActionEvent event) {
			toggleFrame();
		}

		/**
		 * Toggle the status of the frame associated with the button
		 */
		public void toggleFrame() {
			try {
				if (frame != null) {
					frame.setIcon(!frame.isIcon());
					frame.setVisible(!frame.isIcon());
					frame.getDesktopPane().getDesktopManager().activateFrame(frame);
					changeColor();
				}
			} catch (PropertyVetoException e1) {
				LOGGER.log(Level.WARNING, "Could not change JInternalFrame icon", e1);
			}
		}

		/**
		 * Change the background color of the button depending on the status of
		 * the associated frame
		 */
		public void changeColor() {
			if (frame != null && (frame.isIcon() || !frame.isVisible())) {
				setBackground(null);
				setForeground(Color.BLACK);
			} else {
				setBackground(Color.WHITE);
				setForeground(new Color(0, 89, 156));
			}
		}

		/**
		 * @return the frame
		 */
		public JInternalFrame getFrame() {
			return frame;
		}

		/**
		 * Set the internal frame associated with the button
		 * 
		 * @param frame
		 *            Frame associated with the button
		 */
		public void setFrame(final JInternalFrame frame) {
			this.frame = frame;
			refresh();
		}
		
		/**
		 * Refresh the name of the button
		 */
		public void refresh() {
			setText(frame.getTitle());
			setToolTipText("Show / hide schema " + frame.getTitle());
		}
	}
}
